/*
 ------------------------------------------------------------------------------
 Copyright (C) 1996-2001 Id Software, Inc.

 This file is part of the Quake source code.

 The Quake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// bounds.h - bounds math library
//


#ifndef __MATH_BOUNDS_H__
#define __MATH_BOUNDS_H__


extern glqBounds		bounds_zero;

class glqBounds {
private:
	glqVec3			b[2];

public:
					glqBounds (void);
	explicit		glqBounds (const glqVec3 &mins, const glqVec3 &maxs);
	explicit		glqBounds (const glqVec3 &origin, const float size);
	explicit		glqBounds (const float min, const float max);
	explicit		glqBounds (const float minX, const float minY, const float minZ, const float maxX, const float maxY, const float maxZ);

	const glqVec3 &	operator [] (const int index) const;
	glqVec3 &		operator [] (const int index);

	glqBounds &		operator = (const glqBounds &bounds);
	bool			operator == (const glqBounds &bounds) const;
	bool			operator != (const glqBounds &bounds) const;

	glqBounds		operator + (const glqBounds &bounds) const;
	glqBounds		operator - (const glqBounds &bounds) const;
	glqBounds		operator + (const glqVec3 &vec) const;
	glqBounds		operator - (const glqVec3 &vec) const;

	glqBounds &		operator += (const glqBounds &bounds);
	glqBounds &		operator -= (const glqBounds &bounds);
	glqBounds &		operator += (const glqVec3 &vec);
	glqBounds &		operator -= (const glqVec3 &vec);

	void			Set (const glqVec3 &mins, const glqVec3 &maxs);
	void			Set (const glqVec3 &origin, const float size);
	void			Set (const float min, const float max);
	void			Set (const float minX, const float minY, const float minZ, const float maxX, const float maxY, const float maxZ);
	void			Clear (void);
	bool			IsCleared (void) const;
	void			Zero (void);
	bool			IsZeroed (void) const;
	void			AddBounds (const glqBounds &bounds);
	void			AddPoint (const glqVec3 &point);
	glqBounds		Expand (const float size) const;
	void			ExpandSelf (const float size);
	glqBounds		Transform (const glqVec3 &origin, const glqMat3 &axis) const;
	void			TransformSelf (const glqVec3 &origin, const glqMat3 &axis);
	glqVec3			GetCenter (void) const;
	float			GetRadius (void) const;
	float			GetVolume (void) const;
	bool			IntersectsBounds (const glqBounds &bounds) const;
	bool			IntersectsSphere (const glqVec3 &center, const float radius) const;
	bool			IntersectsLine (const glqVec3 &start, const glqVec3 &end) const;
	bool			ContainsPoint (const glqVec3 &point) const;

	const glqVec3 &	Mins (void) const;
	const glqVec3 &	Maxs (void) const;

	void			ToPoints (glqVec3 points[8]) const;
	void			FromPoints (const glqVec3 points[8]);

	const float *	ToFloatPtr (void) const;
	float *			ToFloatPtr (void);

	glqSphere		ToSphere (void) const;
	const char *	ToString (int precision = 2) const;
};

INLINE glqBounds::glqBounds (void){

}

INLINE glqBounds::glqBounds (const glqVec3 &mins, const glqVec3 &maxs){

	b[0] = mins;
	b[1] = maxs;
}

INLINE glqBounds::glqBounds (const glqVec3 &origin, const float size){

	b[0][0] = origin[0] - size;
	b[0][1] = origin[1] - size;
	b[0][2] = origin[2] - size;
	b[1][0] = origin[0] + size;
	b[1][1] = origin[1] + size;
	b[1][2] = origin[2] + size;
}

INLINE glqBounds::glqBounds (const float min, const float max){

	b[0][0] = min;
	b[0][1] = min;
	b[0][2] = min;
	b[1][0] = max;
	b[1][1] = max;
	b[1][2] = max;
}

INLINE glqBounds::glqBounds (const float minX, const float minY, const float minZ, const float maxX, const float maxY, const float maxZ){

	b[0][0] = minX;
	b[0][1] = minY;
	b[0][2] = minZ;
	b[1][0] = maxX;
	b[1][1] = maxY;
	b[1][2] = maxZ;
}

INLINE const glqVec3 &glqBounds::operator [] (const int index) const {

	return b[index];
}

INLINE glqVec3 &glqBounds::operator [] (const int index){

	return b[index];
}

INLINE glqBounds &glqBounds::operator = (const glqBounds &bounds){

	b[0] = bounds[0];
	b[1] = bounds[1];

	return *this;
}

INLINE bool glqBounds::operator == (const glqBounds &bounds) const {

	if (b[0] != bounds[0] || b[1] != bounds[1])
		return false;

	return true;
}

INLINE bool glqBounds::operator != (const glqBounds &bounds) const {

	if (b[0] == bounds[0] && b[1] == bounds[1])
		return false;

	return true;
}

INLINE glqBounds glqBounds::operator + (const glqBounds &bounds) const {

	glqBounds	result;

	result[0] = b[0];
	result[1] = b[1];

	if (bounds[0][0] < result[0][0])
		result[0][0] = bounds[0][0];
	if (bounds[0][1] < result[0][1])
		result[0][1] = bounds[0][1];
	if (bounds[0][2] < result[0][2])
		result[0][2] = bounds[0][2];

	if (bounds[1][0] > result[1][0])
		result[1][0] = bounds[1][0];
	if (bounds[1][1] > result[1][1])
		result[1][1] = bounds[1][1];
	if (bounds[1][2] > result[1][2])
		result[1][2] = bounds[1][2];

	return result;
}

INLINE glqBounds glqBounds::operator - (const glqBounds &bounds) const {

	return glqBounds(b[0] + bounds[1], b[1] + bounds[0]);
}

INLINE glqBounds glqBounds::operator + (const glqVec3 &vec) const {

	return glqBounds(b[0] + vec, b[1] + vec);
}

INLINE glqBounds glqBounds::operator - (const glqVec3 &vec) const {

	return glqBounds(b[0] - vec, b[1] - vec);
}

INLINE glqBounds &glqBounds::operator += (const glqBounds &bounds){

	if (bounds[0][0] < b[0][0])
		b[0][0] = bounds[0][0];
	if (bounds[0][1] < b[0][1])
		b[0][1] = bounds[0][1];
	if (bounds[0][2] < b[0][2])
		b[0][2] = bounds[0][2];

	if (bounds[1][0] > b[1][0])
		b[1][0] = bounds[1][0];
	if (bounds[1][1] > b[1][1])
		b[1][1] = bounds[1][1];
	if (bounds[1][2] > b[1][2])
		b[1][2] = bounds[1][2];

	return *this;
}

INLINE glqBounds &glqBounds::operator -= (const glqBounds &bounds){

	b[0] += bounds[1];
	b[1] += bounds[0];

	return *this;
}

INLINE glqBounds &glqBounds::operator += (const glqVec3 &vec){

	b[0] += vec;
	b[1] += vec;

	return *this;
}

INLINE glqBounds &glqBounds::operator -= (const glqVec3 &vec){

	b[0] -= vec;
	b[1] -= vec;

	return *this;
}

INLINE void glqBounds::Set (const glqVec3 &mins, const glqVec3 &maxs){

	b[0] = mins;
	b[1] = maxs;
}

INLINE void glqBounds::Set (const glqVec3 &origin, const float size){

	b[0][0] = origin[0] - size;
	b[0][1] = origin[1] - size;
	b[0][2] = origin[2] - size;
	b[1][0] = origin[0] + size;
	b[1][1] = origin[1] + size;
	b[1][2] = origin[2] + size;
}

INLINE void glqBounds::Set (const float min, const float max){

	b[0][0] = min;
	b[0][1] = min;
	b[0][2] = min;
	b[1][0] = max;
	b[1][1] = max;
	b[1][2] = max;
}

INLINE void glqBounds::Set (const float minX, const float minY, const float minZ, const float maxX, const float maxY, const float maxZ){

	b[0][0] = minX;
	b[0][1] = minY;
	b[0][2] = minZ;
	b[1][0] = maxX;
	b[1][1] = maxY;
	b[1][2] = maxZ;
}

INLINE void glqBounds::Clear (void){

	b[0][0] = b[0][1] = b[0][2] = M_INFINITY;
	b[1][0] = b[1][1] = b[1][2] = -M_INFINITY;
}

INLINE bool glqBounds::IsCleared (void) const {

	if (b[0][0] <= b[1][0] || b[0][1] <= b[1][1] || b[0][2] <= b[1][2])
		return false;

	return true;
}

INLINE void glqBounds::Zero (void){

	b[0][0] = b[0][1] = b[0][2] = 0.0f;
	b[1][0] = b[1][1] = b[1][2] = 0.0f;
}

INLINE bool glqBounds::IsZeroed (void) const {

	if (b[0][0] != 0.0f || b[0][1] != 0.0f || b[0][2] != 0.0f)
		return false;
	if (b[1][0] != 0.0f || b[1][1] != 0.0f || b[1][2] != 0.0f)
		return false;

	return true;
}

INLINE void glqBounds::AddBounds (const glqBounds &bounds){

	if (bounds[0][0] < b[0][0])
		b[0][0] = bounds[0][0];
	if (bounds[0][1] < b[0][1])
		b[0][1] = bounds[0][1];
	if (bounds[0][2] < b[0][2])
		b[0][2] = bounds[0][2];

	if (bounds[1][0] > b[1][0])
		b[1][0] = bounds[1][0];
	if (bounds[1][1] > b[1][1])
		b[1][1] = bounds[1][1];
	if (bounds[1][2] > b[1][2])
		b[1][2] = bounds[1][2];
}

INLINE void glqBounds::AddPoint (const glqVec3 &point){

	if (point[0] < b[0][0])
		b[0][0] = point[0];
	if (point[0] > b[1][0])
		b[1][0] = point[0];

	if (point[1] < b[0][1])
		b[0][1] = point[1];
	if (point[1] > b[1][1])
		b[1][1] = point[1];

	if (point[2] < b[0][2])
		b[0][2] = point[2];
	if (point[2] > b[1][2])
		b[1][2] = point[2];
}

INLINE glqBounds glqBounds::Expand (const float size) const {

	glqBounds	result;

	result[0][0] = b[0][0] - size;
	result[0][1] = b[0][1] - size;
	result[0][2] = b[0][2] - size;
	result[1][0] = b[1][0] + size;
	result[1][1] = b[1][1] + size;
	result[1][2] = b[1][2] + size;

	return result;
}

INLINE void glqBounds::ExpandSelf (const float size){

	b[0][0] -= size;
	b[0][1] -= size;
	b[0][2] -= size;
	b[1][0] += size;
	b[1][1] += size;
	b[1][2] += size;
}

INLINE glqBounds glqBounds::Transform (const glqVec3 &origin, const glqMat3 &axis) const {

	glqVec3	center, transformedCenter;
	glqVec3	extents, transformedExtents;
	int		i;

	center = (b[0] + b[1]) * 0.5f;
	extents = b[1] - center;

	for (i = 0; i < 3; i++){
		transformedCenter[i] = (center[0] * axis[0][i] + center[1] * axis[1][i] + center[2] * axis[2][i]) + origin[i];
		transformedExtents[i] = glqMath::FAbs(extents[0] * axis[0][i]) + glqMath::FAbs(extents[1] * axis[1][i]) + glqMath::FAbs(extents[2] * axis[2][i]);
	}

	return glqBounds(transformedCenter - transformedExtents, transformedCenter + transformedExtents);
}

INLINE void glqBounds::TransformSelf (const glqVec3 &origin, const glqMat3 &axis){

	glqVec3	center, transformedCenter;
	glqVec3	extents, transformedExtents;
	int		i;

	center = (b[0] + b[1]) * 0.5f;
	extents = b[1] - center;

	for (i = 0; i < 3; i++){
		transformedCenter[i] = (center[0] * axis[0][i] + center[1] * axis[1][i] + center[2] * axis[2][i]) + origin[i];
		transformedExtents[i] = glqMath::FAbs(extents[0] * axis[0][i]) + glqMath::FAbs(extents[1] * axis[1][i]) + glqMath::FAbs(extents[2] * axis[2][i]);
	}

	b[0] = transformedCenter - transformedExtents;
	b[1] = transformedCenter + transformedExtents;
}

INLINE glqVec3 glqBounds::GetCenter (void) const {

	return glqVec3((b[0] + b[1]) * 0.5f);
}

INLINE float glqBounds::GetRadius (void) const {

	float	min, max;
	float	radius = 0.0f;
	int		i;

	for (i = 0; i < 3; i++){
		min = glqMath::FAbs(b[0][i]);
		max = glqMath::FAbs(b[1][i]);

		if (min > max)
			radius += Square(min);
		else
			radius += Square(max);
	}

	return glqMath::Sqrt(radius);
}

INLINE float glqBounds::GetVolume (void) const {

	if (b[0][0] > b[1][0] || b[0][1] > b[1][1] || b[0][2] > b[1][2])
		return 0.0f;

	return (b[1][0] - b[0][0]) * (b[1][1] - b[0][1]) * (b[1][2] - b[0][2]);
}

INLINE bool glqBounds::IntersectsBounds (const glqBounds &bounds) const {

	if (b[0][0] > bounds[1][0] || b[0][1] > bounds[1][1] || b[0][2] > bounds[1][2])
		return false;
	if (b[1][0] < bounds[0][0] || b[1][1] < bounds[0][1] || b[1][2] < bounds[0][2])
		return false;

	return true;
}

INLINE bool glqBounds::IntersectsSphere (const glqVec3 &center, const float radius) const {

	float	d = 0.0f;

	if (center[0] < b[0][0])
		d += Square(center[0] - b[0][0]);
	else if (center[0] > b[1][0])
		d += Square(b[1][0] - center[0]);

	if (center[1] < b[0][1])
		d += Square(center[1] - b[0][1]);
	else if (center[1] > b[1][1])
		d += Square(b[1][1] - center[1]);

	if (center[2] < b[0][2])
		d += Square(center[2] - b[0][2]);
	else if (center[2] > b[1][2])
		d += Square(b[1][2] - center[2]);

	if (d > Square(radius))
		return false;

	return true;
}

INLINE bool glqBounds::IntersectsLine (const glqVec3 &start, const glqVec3 &end) const {

	glqVec3	extents = (b[1] - b[0]) * 0.5f;
	glqVec3	dir = (end - start) * 0.5f;
	glqVec3	absDir;
	glqVec3	vec;

	absDir.Set(glqMath::FAbs(dir[0]), glqMath::FAbs(dir[1]), glqMath::FAbs(dir[2]));

	vec = start + dir - (b[0] + b[1]) * 0.5f;

	if (glqMath::FAbs(vec[0]) > extents[0] + absDir[0])
		return false;
	if (glqMath::FAbs(vec[1]) > extents[1] + absDir[1])
		return false;
	if (glqMath::FAbs(vec[2]) > extents[2] + absDir[2])
		return false;

	if (glqMath::FAbs(dir[1] * vec[2] - dir[2] * vec[1]) > extents[1] * absDir[2] + extents[2] * absDir[1])
		return false;
	if (glqMath::FAbs(dir[2] * vec[0] - dir[0] * vec[2]) > extents[2] * absDir[0] + extents[0] * absDir[2])
		return false;
	if (glqMath::FAbs(dir[0] * vec[1] - dir[1] * vec[0]) > extents[0] * absDir[1] + extents[1] * absDir[0])
		return false;

	return true;
}

INLINE bool glqBounds::ContainsPoint (const glqVec3 &point) const {

	if (b[0][0] > point[0] || b[0][1] > point[1] || b[0][2] > point[2])
		return false;
	if (b[1][0] < point[0] || b[1][1] < point[1] || b[1][2] < point[2])
		return false;

	return true;
}

INLINE const glqVec3 &glqBounds::Mins (void) const {

	return b[0];
}

INLINE const glqVec3 &glqBounds::Maxs (void) const {

	return b[1];
}

INLINE const float *glqBounds::ToFloatPtr (void) const {

	return b[0].ToFloatPtr();
}

INLINE float *glqBounds::ToFloatPtr (void){

	return b[0].ToFloatPtr();
}


#endif	// __MATH_BOUNDS_H__